package subcommands

import (
	"fmt"
	"rc/logger"
	"rc/remote"
	"rc/remote/param"
	"rc/repo"
	"rc/repo/model"
	"rc/utils"
	"time"

	flags "github.com/jessevdk/go-flags"
	"github.com/urfave/cli"
)

func ExecCli(c *cli.Context) error {

	return execNodes(c.String("n"),
		c.String("g"),
		c.String("u"),
		c.String("e"),
		c.String("c"),
		c.Bool("i"))
}

// Exec exec command for group or node
func Exec(args []string) error {
	var (
		err error
		log = logger.GetLogger()
	)

	// opts for exec subcommand
	var opts struct {
		Group       string `short:"g" long:"group" description:"group name"`
		Node        string `short:"n" long:"node" description:"node name"`
		LoginUser   string `short:"u" long:"user" description:"user for login node, default is the node's first user info"`
		ExecUser    string `short:"e" description:"user for exec command, default is the Login User"`
		Cmd         string `short:"c" long:"cmd" description:"command string" required:"true"`
		Interactive bool   `short:"i" description:"excute command in interactive mode or not"`
	}

	var parser = flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash)
	parser.Command.Name = "exec" // set current command name
	_, err = parser.ParseArgs(args)
	if err != nil {
		if err.(*flags.Error).Type == flags.ErrHelp {
			fmt.Println(err.Error())
			return nil
		}
		log.Debug("parser error : %#v\n", err)
		return err
	}
	log.Debug("opts: %v\n", opts)

	return execNodes(opts.Node,
		opts.Group,
		opts.LoginUser,
		opts.ExecUser,
		opts.Cmd,
		opts.Interactive)
}

func execNodes(nodeName, groupName, loginUserName, execUserName, cmd string, interactive bool) error {
	var (
		err      error
		r        repo.Repo
		userInfo model.UserInfo
		nodes    []model.Node
		log      = logger.GetLogger()
	)

	// get nodes for exec
	r = repo.GetRepo()
	if r == nil {
		return fmt.Errorf("may be repo is empty, default repo is : ~/.rc.json")
	}

	nodes, err = r.GetNodes(nodeName, groupName)
	if err != nil {
		return err
	}
	log.Debug("nodes: %#v\n", nodes)

	if userInfo, err = validateUserParam(nodes, loginUserName); err != nil {
		return err
	}

	// exec command
	if interactive {
		return execInteractive(nodes, userInfo, execUserName, cmd)
	}
	return execNoInteractive(nodes, userInfo, execUserName, cmd)
}

func execNoInteractive(nodes []model.Node, userInfo model.UserInfo, user, cmd string) error {
	var re remote.Remote
	var results = make([]*param.ExecOutput, 0)
	var execStart = time.Now()
	for _, n := range nodes {
		re = remote.New(userInfo.UserName, userInfo.Password, userInfo.KeyPath, n.IPv4, n.Port)
		result := re.Exec(user, cmd)
		displayExecResult(result)
		results = append(results, result)
	}

	// display result
	displayTotalExecResult(results, execStart, time.Now())
	return nil
}

func execInteractive(nodes []model.Node, userInfo model.UserInfo, user, cmd string) error {
	if len(nodes) == 0 {
		return fmt.Errorf("No node to execute command")
	}

	var re remote.Remote
	var n = nodes[0]
	re = remote.New(userInfo.UserName, userInfo.Password, userInfo.KeyPath, n.IPv4, n.Port)

	return re.ExecInteractive(user, cmd)
}

func validateUserParam(nodes []model.Node, userName string) (model.UserInfo, error) {
	var errMsg = "Node[%s] can not login with user[%s]\n"
	var userInfo = model.UserInfo{}

	if len(nodes) == 0 || len(nodes[0].UserInfos) == 0 {
		return userInfo, fmt.Errorf("No node to exec command")
	}

	// set login user
	if userName == "" {
		userInfo = nodes[0].UserInfos[0]
	} else {
		for _, ui := range nodes[0].UserInfos {
			if ui.UserName == userName {
				userInfo = ui
				break
			}
		}
	}

	for _, n := range nodes {
		if len(n.UserInfos) == 0 {
			return userInfo, fmt.Errorf(errMsg, n.Name, userInfo.UserName)
		}

		var findLoginUser = false
		for _, ui := range n.UserInfos {
			if ui.UserName == userInfo.UserName {
				findLoginUser = true
				break
			}
		}
		if !findLoginUser {
			return userInfo, fmt.Errorf(errMsg, n.Name, userInfo.UserName)
		}
	}

	return userInfo, nil
}

func displayExecResult(output *param.ExecOutput) {
	if output.Err != nil {
		fmt.Printf("Command exec failed: %s\n", utils.FgRed(output.Err))
	}

	if output != nil {
		fmt.Printf(">>>>>>>>>>>>>>>>>>>> STDOUT >>>>>>>>>>>>>>>>>>>>\n%s\n", output.StdOutput)
		if output.Status == param.Fail {
			fmt.Printf(">>>>>>>>>>>>>>>>>>>> STDERR >>>>>>>>>>>>>>>>>>>>\n%s\n", output.StdError)
		}
		fmt.Printf("time costs: %v\n", output.ExecEnd.Sub(output.ExecStart))
	}
	fmt.Println(utils.FgBoldBlue("==========================================================\n"))
}

func displayTotalExecResult(outputs []*param.ExecOutput, execStart, execEnd time.Time) {
	var successCnt, failCnt, timeoutCnt int

	for _, output := range outputs {
		switch output.Status {
		case param.Success:
			successCnt += 1
		case param.Fail:
			failCnt += 1
		case param.Timeout:
			timeoutCnt += 1
		}
	}

	fmt.Printf("total time costs: %v\nEXEC success nodes: %s | fail nodes: %s | timeout nodes: %s\n\n\n",
		execEnd.Sub(execStart),
		utils.FgBoldGreen(successCnt),
		utils.FgBoldRed(failCnt),
		utils.FgBoldYellow(timeoutCnt))
}
